#include "StdAfx.h"
#include "ExMesh.h"
#include "MaxExporter.h"

extern CMaxExporterApp theApp;

ExMesh::ExMesh(void)
	:m_pD3DXMESH( NULL )
{
}

ExMesh::~ExMesh(void)
{
}

bool ExMesh::Init( INode* pMaxNode, Mesh* pMaxMesh, TimeValue timeNow, bool bSnapVertices )
{
	assert( pMaxNode != NULL && pMaxMesh != NULL );
	bool ret = false;

	bool bIsStatic = false;

	m_strMeshId = pMaxNode->GetName();

	if (pMaxMesh->getNumFaces() && pMaxMesh->getNumVerts())
	{
		//identifier_ = trimWhitespaces( node->GetName() );
		//gatherMaterials( node );
		
		bool hasUVs = pMaxMesh->getNumTVerts() && pMaxMesh->tvFace;

		BloatVertex bv;
		bv.normal = Point3::Origin;
		bv.uv = Point2::Origin;
		bv.binormal = Point3::Origin;
		bv.tangent = Point3::Origin;

		//transform to DX space
		Matrix3 meshMatrix = pMaxNode->GetObjectTM( timeNow );
		Matrix3 nodeMatrix = pMaxNode->GetNodeTM( timeNow );

		for (int i = 0; i < pMaxMesh->getNumFaces(); i++)
		{
			Face face = pMaxMesh->faces[ i ];
			Triangle tr;
			tr.materialIndex = 0;
			tr.smoothingGroup = face.smGroup;
			for (int j = 0; j < 3; j++)
			{
				bv.pos = pMaxMesh->verts[ face.v[j] ];
				bv.vertexIndex = face.v[j];
				bv.smoothingGroup = face.smGroup;
				if (hasUVs)
				{
					bv.uv = reinterpret_cast<Point2&>( pMaxMesh->tVerts[ pMaxMesh->tvFace[i].t[j] ] );
					bv.uv.y = 1 - bv.uv.y;
				}
				bv.meshIndex = 0;

				if (bSnapVertices)
				{
					Common::Vector3 vSnappedPos = ExUtilityFunction::SnapPoint3( bv.pos );
					bv.pos = Point3( vSnappedPos.x, vSnappedPos.y, vSnappedPos.z );
				}
				tr.index[j] = _AddVertex( bv );
				tr.realIndex[j] = bv.vertexIndex;
			}
			m_vTriangles.push_back( tr );
		}

		bool nodeMirrored = bIsStatic ? false : ExUtilityFunction::IsMirrored( nodeMatrix );
		bool meshMirrored = ExUtilityFunction::IsMirrored( meshMatrix );

		if (nodeMirrored ^ meshMirrored)
			_FlipTriangleWindingOrder();

		for( BVVector::size_type i = 0; i < m_vVertices.size(); ++i )
			m_mapNormalInfluencedVerticesIndex[ m_vVertices[ i ].vertexIndex ].push_back( i );

		_CreateNormals();
		//getNormalsFromModifier( node );

		if (bIsStatic)
			m_matWorld.IdentityMatrix();
		else
			m_matWorld = nodeMatrix;

		// initialise boundingbox
		if (bSnapVertices)
			m_aabb.setExtents( ExUtilityFunction::SnapPoint3( m_matWorld * m_vVertices[ 0 ].pos ), ExUtilityFunction::SnapPoint3( m_matWorld * m_vVertices[ 0 ].pos ) );
		else
			m_aabb.setExtents( ExUtilityFunction::SnapPoint3( m_matWorld * m_vVertices[ 0 ].pos ), ExUtilityFunction::SnapPoint3( m_matWorld * m_vVertices[ 0 ].pos ) );
		for (uint32 i = 1; i < m_vVertices.size(); i++)
		{
			if (bSnapVertices)
				m_aabb.merge( ExUtilityFunction::SnapPoint3( m_matWorld * m_vVertices[ i ].pos ) );
			else
				m_aabb.merge( ExUtilityFunction::SnapPoint3( m_matWorld * m_vVertices[ i ].pos ) );
		}

		if (nodeMirrored)
			_FlipTriangleWindingOrder();

		ret = true;
	}

	return ret;
}

void ExMesh::_FlipTriangleWindingOrder()
{
	TriangleVector::iterator it = m_vTriangles.begin();
	TriangleVector::iterator end = m_vTriangles.end();

	while (it!=end)
	{
		Triangle& tri = *it++;
		std::swap( tri.index[1], tri.index[2] );
		std::swap( tri.realIndex[1], tri.realIndex[2] );
	}
}

void ExMesh::_AddNormal( const Point3& normal, int realIndex, int smoothingGroup, int index )
{
	if( m_mapNormalInfluencedVerticesIndex.find( realIndex ) != m_mapNormalInfluencedVerticesIndex.end() )
	{
		std::vector<BVVector::size_type>& verticeIndexSet = m_mapNormalInfluencedVerticesIndex[ realIndex ];
		for( std::vector<BVVector::size_type>::iterator iter = verticeIndexSet.begin();
			iter != verticeIndexSet.end(); ++iter )
		{
			BloatVertex& bv = m_vVertices[ *iter ];
			if( bv.smoothingGroup & smoothingGroup || ((*iter) == index) )
				bv.normal += normal;
		}
	}
}

int ExMesh::_AddVertex( const BloatVertex& bv )
{
	if( m_mapUniqueVertices.find( bv ) != m_mapUniqueVertices.end() )
	{
		std::set<BVVector::size_type>& verticeSet = m_mapUniqueVertices[ bv ];
		for( std::set<BVVector::size_type>::iterator iter = verticeSet.begin();
			iter != verticeSet.end(); ++iter )
			if( m_vVertices[ *iter ] == bv )
			{
				BloatVertex& mbv = m_vVertices[ *iter ];
				mbv.smoothingGroup |= bv.smoothingGroup;
				return *iter;
			}
	}
	m_vVertices.push_back( bv );
	UINT uNewIndex =  m_vVertices.size() - 1;

	m_mapUniqueVertices[ bv ].insert(uNewIndex);
	return uNewIndex;
}

void ExMesh::_CreateNormals()
{
	TriangleVector::iterator it = m_vTriangles.begin();
	TriangleVector::iterator end = m_vTriangles.end();

	while (it!=end)
	{
		Triangle& tri = *it++;
		Point3 v1 = m_vVertices[tri.index[1]].pos - m_vVertices[tri.index[0]].pos;
		Point3 v2 = m_vVertices[tri.index[2]].pos - m_vVertices[tri.index[0]].pos;
		Point3 normal = v1^v2;
		normal = Normalize( normal );

		_AddNormal( normal/* * normalAngle(tri, 0)*/, tri.realIndex[0], tri.smoothingGroup, tri.index[0] );
		_AddNormal( normal/* * normalAngle(tri, 1)*/, tri.realIndex[1], tri.smoothingGroup, tri.index[1] );
		_AddNormal( normal/* * normalAngle(tri, 2)*/, tri.realIndex[2], tri.smoothingGroup, tri.index[2] );
	}

	BVVector::iterator vit = m_vVertices.begin();
	BVVector::iterator vend = m_vVertices.end();
	while (vit != vend)
	{
		BloatVertex& bv = *vit++;
		bv.normal = Normalize( bv.normal );
	}
}

bool ExMesh::Save( const std::string& rModelFilePath, TiXmlNode* pContentSection )
{
	//format

	//<NodeIdentifier>NodeIdentifier</NodeIdentifier>
	//<Model>fileName</Model>

	//which node the model is attached
	TiXmlNode* pAttachNodeSection = pContentSection->InsertEndChild( TiXmlElement("AttachedNode") );
	pAttachNodeSection->InsertEndChild( TiXmlText(m_strMeshId.c_str()) );

	//where is the model resource
	TiXmlNode* pModelResouceSection = pContentSection->InsertEndChild( TiXmlElement("Model") );
	pModelResouceSection->InsertEndChild( TiXmlText(m_strMeshId.c_str()) );

	LPDIRECT3DDEVICE9 pDevice = theApp.GetDevice();
	if( pDevice == NULL )
		return false;
	
	//create mesh
	if( D3DXCreateMeshFVF( m_vTriangles.size(), m_vVertices.size(), D3DXMESH_MANAGED, BV_FVF, pDevice, &m_pD3DXMESH ) != S_OK )
		return false;
	//get vb
	struct VertexFormat
	{
		Point3	pos;
		Point3	normal;
		Point2	uv;
		Point3	tangent;
	};

	VertexFormat* pVB = NULL;
	if( S_OK != m_pD3DXMESH->LockVertexBuffer( D3DLOCK_DISCARD, (LPVOID*)&pVB ) )
		return false;

	for( UINT32 uVertIdx = 0; uVertIdx != m_vVertices.size(); uVertIdx++ )
	{
		VertexFormat& rDXMeshVtx = pVB[uVertIdx];
		BloatVertex pMaxMeshVtx = m_vVertices[uVertIdx];

		rDXMeshVtx.pos = ExUtilityFunction::MaxToDx( ExUtilityFunction::ApplyUnitScale( pMaxMeshVtx.pos ) );
		rDXMeshVtx.normal = ExUtilityFunction::MaxToDx( pMaxMeshVtx.normal );
		rDXMeshVtx.uv = pMaxMeshVtx.uv;
		rDXMeshVtx.tangent = ExUtilityFunction::MaxToDx( pMaxMeshVtx.tangent );
	}

	//get ib
	UINT16* pIB = NULL;
	if( S_OK != m_pD3DXMESH->LockIndexBuffer( D3DLOCK_DISCARD, (LPVOID*)&pIB ) )
		return false;

	for( UINT16 uFaceIndex = 0; uFaceIndex != m_vTriangles.size(); ++uFaceIndex )
	{
		for( int i = 0; i != 3; ++i )
		{
			//face flip
			pIB[ uFaceIndex * 3 + i ] = m_vTriangles[uFaceIndex].index[i];
			//pIB[ uFaceIndex * 3 + ( 3 - i ) % 3 ] = m_vTriangles[uFaceIndex].index[i];
		}
	}

	//save mesh
	if( S_OK != D3DXSaveMeshToX( ( rModelFilePath + m_strMeshId + ".x" ).c_str(), m_pD3DXMESH, NULL, NULL, NULL, NULL, D3DXF_FILESAVE_TOFILE|D3DXF_FILEFORMAT_TEXT ) )
		return false;

	return true;
}

void ExMesh::Merge( ExMesh* pDestMesh )
{
	if( pDestMesh == NULL )
		return;

	// append our transformed vertices to its vertices
	uint32 startVertex = pDestMesh->m_vVertices.size();
	for ( uint32 iIdx = 0; iIdx < startVertex; iIdx++)
	{
		BloatVertex bv = pDestMesh->m_vVertices[iIdx];
		bv.pos = m_matWorld.PointTransform( bv.pos );
		// normal should be transformed by world I T?
		bv.normal = Normalize( m_matWorld.VectorTransform( bv.normal ) );
		//bv.meshIndex = iStartIndex;

		pDestMesh->m_vVertices.push_back( bv );
	}

	// append our remapped and shuffled triangles to its triangles
	for ( uint32 uTriIdx = 0; uTriIdx < m_vTriangles.size(); uTriIdx++)
	{
		Triangle tr = m_vTriangles[uTriIdx];
		tr.index[0] += startVertex;
		tr.index[1] += startVertex;
		tr.index[2] += startVertex;
		pDestMesh->m_vTriangles.push_back( tr );
	}

}

const Common::AxisAlignedBox& ExMesh::GetBoundingBox( void ) const
{
	return m_aabb;
}